from utils import MAXFLOAT, floatcompare as compare
from utils.prettyrepr import PrettyMeta


class Line(object):
    """Class for NON-VERTICAL infinite lines, line segments, and semi-open segments. A segment 
    should be treated as an immutable object, i.e. once it has been created, its attributes should 
    not be modified."""
    __metaclass__ = PrettyMeta
    
    def __init__(self, x0, y0, x1, y1, open_left=True, open_right=True):
        if compare.eq(x0, x1):
            raise Exception("x0 and x1 cannot be equal")
        if compare.gt(x0, x1):
            raise Exception()
        
        self.x0 = x0
        self.y0 = y0
        self.x1 = x1
        self.y1 = y1
        self.open_left = open_left
        self.open_right = open_right
        
        if self.x0 > self.x1: # swap p0 and p1 if necessary, so that x0 <= x1
            
            self.x0, self.x1 = self.x1, self.x0
            self.y0, self.y1 = self.y1, self.y0
        self.dx = dx = float(self.x1 - self.x0)
        self.dy = dy = float(self.y1 - self.y0)
        self.m = m = dy / dx
        self.b = self.y0 - m * self.x0

    def __info__(self):
        if self.open_left and self.open_right:
            return "]-inf, +inf[, m=%s, b=%s" % (self.m, self.b)
        if self.open_left:
            return "]-inf, %s], m=%s, b=%s" % (self.p1, self.m, self.b)
        if self.open_right:
            return "[%s, +inf[, m=%s, b=%s" % (self.p0, self.m, self.b)
        return "[%s, %s], m=%s, b=%s" % (self.p0, self.p1, self.m, self.b)

    @classmethod
    def segment(cls, x0, y0, x1, y1, open_left=False, open_right=False):
        return cls(x0, y0, x1, y1, open_left, open_right)
        
    @classmethod
    def from_point(cls, x, y, m, open_left=True, open_right=True):
        """Create a line from a point and a slope."""
        return cls(x, y, x + 1.0, y + m, open_left, open_right)
        
    def _update(self):
        
    def __eq__(self, segment):
        if self.open_left != segment.open_left or self.open_right != segment.open_right:
            return False
        if self.open_left and self.open_right:
            return (compare.eq(self.m, segment.m) and 
                    compare.eq(self.b, segment.b))
        if not self.open_right:
            return (compare.eq(self.m, segment.m) and 
                    compare.eq(self.b, segment.b) and
                    compare.eq(self.x1, segment.x1) and 
                    compare.eq(self.y1, segment.y1))

        return all(compare.eq(getattr(self, attr), getattr(segment, attr)) 
                   for attr in ["x0", "x1", "y0", "y1", "dx", "dy", "m", "b"])

    def __ne__(self, segment):
        return not self.__eq__(segment)
        
    @property
    def p0(self):
        return (self.x0, self.y0)
        
    @property
    def p1(self):
        return (self.x1, self.y1)
        
    def xmin(self):
        return -MAXFLOAT if self.open_left else self.x0
        
    def xmax(self):
        return +MAXFLOAT if self.open_right else self.x1
        
    def x_at(self, y):
        return float(y - self.b) / self.m
        
    def y_at(self, x):
        return self.m * x + self.b
        
    def intersection(self, other):
        if compare.eq(self.m, other.m):
            return None
        x = float(other.b - self.b) / (self.m - other.m)
        if (compare.geq(x, max(self.xmin, other.xmin)) and 
            compare.leq(x, min(self.xmax, other.xmax))):
            return (x, self.y_at(x))
        return None
        
    def plot(self, target=None, **kwargs):
        from khronos.statistics.plotter import get_plotter
        plt = get_plotter(target)
        axes = plt.line_plot([self.x0, self.x1], [self.y0, self.y1], **kwargs)
        return axes
        
        
